#include <tinx/fs.h>
#include <tinx/device.h>
#include <tinx/buffer.h>
#include <tinx/task.h>
#include <tinx/user.h>
#include <tinx/memory.h>
#include <tinx/stdlib.h>
#include <tinx/arena.h>
#include <tinx/string.h>
#include <tinx/time.h>

#include "ext2.h"

static u32 ext2_group_ialloc(super_t *super, u32 group)
{
    ext2_data_t *data = super->data;
    ext2_desc_t *desc = super->desc;
    ext2_group_info_t *info = &data->group_infos[group];
    if (!info->desc->free_inodes)
        return EOF;

    buffer_t *buf = info->imap;
    assert(buf);

    bitmap_t map;
    bitmap_make(&map, buf->data, data->block_size, group * desc->group_inodes + 1);

    u32 bit = bitmap_scan(&map, 1);

    if (bit != EOF)
    {
        info->desc->free_inodes--;
        data->groups_buf->dirty = true;
        bwrite(data->groups_buf);

        buf->dirty = true;
        bwrite(buf);
    }

    return bit;
}

// group 作为第一个扫描的组
// 如果没有就从组 0 开始
u32 ext2_ialloc(super_t *super, u32 group)
{
    assert(group >= 0);

    ext2_data_t *data = super->data;

    u32 bit = ext2_group_ialloc(super, group);
    if (bit != EOF)
        return bit;

    for (int i = 0; i < data->total_groups; i++)
    {
        if (i == group)
            continue;
        u32 bit = ext2_group_ialloc(super, i);
        if (bit != EOF)
            break;
    }
    return bit;
}

static u32 ext2_group_balloc(super_t *super, u32 group)
{
    ext2_data_t *data = super->data;
    ext2_desc_t *desc = super->desc;
    ext2_group_info_t *info = &data->group_infos[group];
    if (!info->desc->free_blocks)
        return EOF;

    buffer_t *buf = info->zmap;
    assert(buf);

    bitmap_t map;
    bitmap_make(&map, buf->data, data->block_size, group * desc->group_blocks);

    u32 bit = bitmap_scan(&map, 1);

    if (bit != EOF)
    {
        info->desc->free_blocks--;
        data->groups_buf->dirty = true;
        bwrite(data->groups_buf);

        buf->dirty = true;
        bwrite(buf);
    }

    return bit;
}

// group 作为第一个扫描的组
// 如果没有就从组 0 开始
u32 ext2_balloc(super_t *super, u32 group)
{
    assert(group >= 0);

    ext2_data_t *data = super->data;

    u32 bit = ext2_group_balloc(super, group);
    if (bit != EOF)
        return bit;

    for (int i = 0; i < data->total_groups; i++)
    {
        if (i == group)
            continue;
        u32 bit = ext2_group_balloc(super, i);
        if (bit != EOF)
            break;
    }
    return bit;
}

void ext2_ifree(super_t *super, u32 nr)
{
    ext2_desc_t *desc = super->desc;
    ext2_data_t *data = super->data;

    u32 group = INODE_TO_GROUP(nr, desc);
    ext2_group_info_t *info = &data->group_infos[group];
    buffer_t *buf = info->imap;

    bitmap_t map;
    bitmap_make(&map, buf->data, data->block_size, group * desc->group_inodes + 1);
    bitmap_set(&map, nr, false);

    info->desc->free_inodes--;
    data->groups_buf->dirty = true;
    bwrite(data->groups_buf);

    buf->dirty = true;
    bwrite(buf);
}

void ext2_bfree(super_t *super, u32 block)
{
    ext2_desc_t *desc = super->desc;
    ext2_data_t *data = super->data;

    u32 group = block / desc->group_blocks;
    ext2_group_info_t *info = &data->group_infos[group];
    buffer_t *buf = info->zmap;

    bitmap_t map;
    bitmap_make(&map, buf->data, data->block_size, group * desc->group_blocks);
    bitmap_set(&map, block, false);

    info->desc->free_inodes--;
    data->groups_buf->dirty = true;
    bwrite(data->groups_buf);

    buf->dirty = true;
    bwrite(buf);
}

u32 ext2_bmap(inode_t *inode, u32 block, bool creat)
{
    super_t *super = inode->super;
    ext2_data_t *data = super->data;
    ext2_desc_t *desc = super->desc;

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;
    u32 *array = (u32 *)einode->zone;
    u32 index = block;

    buffer_t *buf = NULL;
    int level = 0;
    int divider = 1;

    if (block < DIRECT_BLOCK)
    {
        goto reckon;
    }

    block -= DIRECT_BLOCK;
    if (block < INDIRECT1_BLOCK)
    {
        divider = 1;
        level = 1;
        index = DIRECT_BLOCK;
        goto reckon;
    }

    block -= INDIRECT1_BLOCK;
    if (block < INDIRECT2_BLOCK)
    {
        level = 2;
        index = DIRECT_BLOCK + 1;
        divider *= BLOCK_INDEXES;
        goto reckon;
    }

    level = 3;
    index = DIRECT_BLOCK + 2;
    divider *= BLOCK_INDEXES;
    block -= INDIRECT2_BLOCK;

reckon:
    // 如果没有块并且 creat 置位就分配一个块
    if (!array[index] && creat)
    {
        array[index] = ext2_balloc(inode->super, INODE_TO_GROUP(inode->nr, desc));
    }

    // 释放之前的缓冲
    brelse(buf);

    // 层级结束就返回
    if (!level || !array[index])
        return array[index];

    // 获得下一块的缓冲
    buf = bread(inode->dev, array[index], BLOCK_SIZE);

    // 获取下一块的内容
    array = (u32 *)buf->data;
    index = block / divider;
    block %= divider;
    divider /= BLOCK_INDEXES;
    level--;
    goto reckon;
}

inode_t *ext2_iget(super_t *super, u32 nr)
{
    inode_t *inode = find_inode(super, nr);
    if (inode)
    {
        inode->atime = ktime();
        inode->count++;
        return fit_inode(inode);
    }

    ext2_desc_t *desc = super->desc;
    ext2_data_t *data = super->data;

    ext2_group_info_t *info = &data->group_infos[INODE_TO_GROUP(nr, desc)];
    u32 index = INODE_TO_GROUP_INDEX(nr, desc);

    u32 block = info->desc->inode_table + index / BLOCK_INODES;
    u32 offset = index % BLOCK_INODES;

    buffer_t *buf = bread(super->dev, block, data->block_size);
    assert(buf);

    inode = get_free_inode();

    list_append(&super->inode_list, &inode->node);
    ext2_inode_t *einode = (ext2_inode_t *)((u32)buf->data + offset * data->inode_size);

    inode->mtime = einode->mtime;
    inode->ctime = einode->ctime;
    inode->atime = einode->atime = ktime();

    inode->uid = einode->uid;
    inode->gid = einode->gid;
    inode->block_size = data->block_size;
    inode->nlinks = einode->nlinks;
    inode->mode = einode->mode;
    inode->size = einode->size;
    if (ISCHR(inode->mode) || ISBLK(inode->mode))
        inode->rdev = einode->zone[0];

    inode->desc = einode;
    inode->buf = buf;
    inode->super = super;
    inode->nr = nr;
    inode->dev = super->dev;
    inode->op = super->op;
    inode->data = super->data;
    inode->count = 1;

    buf->dirty = true;

    return inode;
}

inode_t *ext2_new_inode(super_t *super, u32 nr)
{
    inode_t *inode = find_inode(super, nr);
    assert(!inode);

    task_t *task = running_task();

    ext2_desc_t *desc = super->desc;
    ext2_data_t *data = super->data;

    ext2_group_info_t *info = &data->group_infos[INODE_TO_GROUP(nr, desc)];
    u32 index = INODE_TO_GROUP_INDEX(nr, desc);

    u32 block = info->desc->inode_table + index / BLOCK_INODES;
    u32 offset = index % BLOCK_INODES;

    buffer_t *buf = bread(super->dev, block, data->block_size);
    assert(buf);

    inode = get_free_inode();

    list_append(&super->inode_list, &inode->node);
    ext2_inode_t *einode = (ext2_inode_t *)((u32)buf->data + offset * data->inode_size);
    memset(einode, 0, data->inode_size);

    einode->atime = einode->ctime = einode->mtime = ktime();
    einode->mode = 0777 & (~task->umask);
    einode->uid = task->uid;
    einode->blocks = 0;
    buf->dirty = true;

    inode->mtime = einode->mtime;
    inode->ctime = einode->ctime;
    inode->atime = einode->atime = ktime();

    inode->uid = einode->uid;
    inode->gid = einode->gid;
    inode->block_size = data->block_size;
    inode->nlinks = einode->nlinks;
    inode->mode = einode->mode;
    inode->size = einode->size;
    if (ISCHR(inode->mode) || ISBLK(inode->mode))
        inode->rdev = einode->zone[0];

    inode->desc = einode;
    inode->buf = buf;
    inode->super = super;
    inode->nr = nr;
    inode->dev = super->dev;
    inode->op = super->op;
    inode->data = super->data;
    inode->count = 1;

    return inode;
}

int ext2_open(inode_t *dir, char *name, int flags, int mode, inode_t **result)
{
    inode_t *inode = NULL;
    ext2_inode_t *einode = NULL;
    char *next = NULL;
    buffer_t *buf = NULL;
    task_t *task = running_task();
    super_t *super = dir->super;
    ext2_desc_t *desc = super->desc;

    int ret = EOK;

    if ((flags & O_TRUNC) && (flags & O_ACCMODE) == O_RDONLY)
        flags |= O_RDWR;

    if (!name[0])
    {
        ret = -ENOENT;
        goto rollback;
    }

    ext2_dentry_t *entry;
    buf = ext2_find_entry(&dir, name, &next, &entry);
    if (buf)
    {
        inode = ext2_iget(dir->super, entry->nr);
        goto makeup;
    }

    if (!(flags & O_CREAT))
    {
        ret = -ENOENT;
        goto rollback;
    }

    buf = ext2_add_entry(dir, name, &entry);
    entry->nr = ext2_ialloc(dir->super, INODE_TO_GROUP(dir->nr, desc));
    inode = ext2_new_inode(dir->super, entry->nr);
    einode = (ext2_inode_t *)inode->desc;

    einode->nlinks++;
    inode->nlinks++;

    mode &= 0777 & ~task->umask;
    mode |= IFREG;

    einode->mode = mode;
    inode->mode = mode;

makeup:
    if (!ISDIR(inode->mode) && next)
    {
        ret = -EISDIR;
        goto rollback;
    }

    if (!einode)
    {
        einode = inode->desc;
    }

    if (!inode->op->permission(inode, ACC_MODE(flags & O_ACCMODE)))
    {
        ret = -EPERM;
        goto rollback;
    }

    if (ISLNK(inode->mode))
    {
        inode_t *linode;
        ext2_lnamei(dir, inode, &linode);
        iput(inode);
        inode = linode;
    }

    if (ISDIR(inode->mode) && ((flags & O_ACCMODE) != O_RDONLY))
    {
        ret = -EISDIR;
        goto rollback;
    }

    inode->atime = ktime();
    einode->atime = inode->atime;

    if (flags & O_TRUNC)
        ext2_truncate(inode);

    *result = inode;

    brelse(buf);
    return ret;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int ext2_close(inode_t *inode)
{
    if (inode->buf->dirty)
    {
        bwrite(inode->buf);
    }
    inode->count--;
    if (inode->count)
    {
        ext2_inode_t *einode = inode->desc;
        inode->size = einode->size;
        inode->nlinks = einode->nlinks;
        inode->rdev = einode->zone[0];
        return EOK;
    }
    inode->super = NULL;
    inode->mount = NULL;
    inode->nr = 0;
    inode->dev = -1;
    brelse(inode->buf);
    list_remove(&inode->node);
    return EOK;
}

int ext2_read(inode_t *inode, void *addr, int count, int offset)
{
    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;
    ext2_data_t *data = inode->data;
    u32 block_size = data->block_size;

    if (offset >= einode->size)
    {
        return -EEOF;
    }

    count = MIN(count, einode->size - offset);
    int left = count;

    while (left)
    {
        if (offset / block_size == 256)
        {
            NOP;
        }
        idx_t block = ext2_bmap(inode, offset / block_size, false);
        buffer_t *buf = bread(inode->dev, block, BLOCK_SIZE);

        int start = offset % BLOCK_SIZE;
        int chars = MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(addr, src, chars);

        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }

    inode->atime = einode->atime = ktime();

    return count;
}

int ext2_write(inode_t *inode, void *addr, int count, int offset)
{
    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;
    ext2_data_t *data = inode->data;
    u32 block_size = data->block_size;

    int stop = offset + count;
    if (stop > einode->size)
    {
        einode->size = inode->size = stop;
    }

    int left = count;

    while (left)
    {
        idx_t block = ext2_bmap(inode, offset / block_size, true);
        buffer_t *buf = bread(inode->dev, block, block_size);

        int start = offset % BLOCK_SIZE;
        int chars =  MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(src, addr, chars);
        buf->dirty = true;

        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }

    inode->buf->dirty = true;
    bwrite(inode->buf);

    einode->mtime = inode->mtime = einode->atime = inode->atime = ktime();

    return count;
}

int ext2_readdir(file_t *file, dentry_t *entry, int count)
{
    inode_t *inode = file->inode;
    buffer_t *buf = NULL;
    ext2_dentry_t *ptr = NULL;

    ext2_data_t *data = inode->data;
    u32 block_size = data->block_size;

    int len = 0;

    for (; file->offset < inode->size; file->offset += ptr->size, ptr = (ext2_dentry_t *)((u32)ptr + ptr->size))
    {
        if (!buf || (u32)ptr >= (u32)buf->data + block_size)
        {
            brelse(buf);
            buf = bread(inode->dev, ext2_bmap(inode, file->offset / block_size, false), block_size);
            ptr = (ext2_dentry_t *)((u32)buf->data + (file->offset % BLOCK_SIZE));
        }
        if (!ptr->nr)
        {
            continue;
        }

        u32 size = sizeof(dentry_t) + ptr->namelen;

        if (count - len <= size && count)
        {
            break;
        }

        // 填充 entry
        memset(entry, 0, size);
        entry->nr = ptr->nr;
        entry->length = size;
        entry->namelen = ptr->namelen;
        memcpy(entry->name, ptr->name, entry->namelen);

        entry = (dentry_t *)((u32)entry + size);
        len += size;

        if (!count)
        {
            file->offset += ptr->size;
            break;
        }
    }
    brelse(buf);
    return len;
}

void ext2_inode_bfree(inode_t *inode, u32 *array, u32 index, int level)
{
    if (!array[index])
        return;

    if (!level)
    {
        ext2_bfree(inode->super, array[index]);
        array[index] = 0;
        return;
    }

    ext2_data_t *data = inode->data;

    buffer_t *buf = bread(inode->dev, array[index], BLOCK_SIZE);
    for (size_t i = 0; i < BLOCK_INDEXES; i++)
    {
        ext2_inode_bfree(inode, (u32 *)buf->data, i, level - 1);
    }
    brelse(buf);
    ext2_bfree(inode->super, array[index]);
    array[index] = 0;
}

int ext2_truncate(inode_t *inode)
{
    assert(ISDIR(inode->mode) || ISFILE(inode->mode));

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;

    u32 *array = einode->zone;
    for (size_t i = 0; i < DIRECT_BLOCK; i++)
    {
        ext2_inode_bfree(inode, array, i, 0);
    }

    ext2_inode_bfree(inode, array, DIRECT_BLOCK, 1);
    ext2_inode_bfree(inode, array, DIRECT_BLOCK + 1, 2);
    ext2_inode_bfree(inode, array, DIRECT_BLOCK + 2, 3);

    einode->size = inode->size = 0;
    einode->blocks = 0;
    einode->mtime = ktime();
    inode->mtime = einode->mtime;
    einode->atime = einode->mtime;
    inode->atime = einode->atime;

    inode->buf->dirty = true;
    bwrite(inode->buf);
    return EOK;
}

int ext2_match(char *name, char *entry_name, char **next, u8 namelen)
{
    char *lhs = (char *)name;
    char *rhs = (char *)entry_name;
    // 循环比较
    int count = namelen;
    while (count && *lhs == *rhs)
    {
        lhs++;
        rhs++;
        count--;
    }
    // 如果有剩下的字符，但是下一个不是分割符返回 false
    if (*lhs && !IS_SEPARATOR(*lhs))
        return false;
    // 是分隔符就获得下一个节点的名字, 并且返回 true
    if (IS_SEPARATOR(*lhs))
    {
        lhs++;
        *next = lhs;
    }
    return true;
}

buffer_t *ext2_find_entry(inode_t **dir, char *name, char **next, ext2_dentry_t **entry)
{
    if (match_name(name, "..", next) && (*dir)->nr == EXT2_ROOT_INODE)
    {
        inode_t *inode = (*dir)->super->imount;
        inode->count++;
        iput(*dir);
        (*dir) = inode;
    }

    ext2_data_t *data = (*dir)->data;
    u32 block_size = data->block_size;

    ext2_dentry_t *ptr;
    buffer_t *buf = NULL;

    u32 namelen = strnamelen(name);

    for (
        u32 offset = 0;
        offset < (*dir)->size;
        offset += ptr->size, ptr = (ext2_dentry_t *)((u32)ptr + ptr->size))
    {
        if (!buf || (u32)ptr >= (u32)buf->data + block_size)
        {
            brelse(buf);
            buf = bread((*dir)->dev, ext2_bmap(*dir, offset / block_size, false), block_size);
            ptr = (ext2_dentry_t *)buf->data;
            assert(offset % block_size == 0);
        }
        if (!ptr->nr)
            continue;
        if (ptr->namelen != namelen)
            continue;
        if (!ext2_match(name, ptr->name, next, ptr->namelen))
            continue;
        *entry = ptr;
        return buf;
    }
    brelse(buf);
    return NULL;
}

buffer_t *ext2_add_entry(inode_t *dir, char *name, ext2_dentry_t **entry)
{
    ext2_data_t *data = dir->data;
    u32 block_size = data->block_size;

    ext2_dentry_t *ptr;
    buffer_t *buf = NULL;

    char *next=  NULL;

    u32 namelen = strnamelen(name);
    u32 entry_size = ENTRY_LEN(namelen);

    ext2_inode_t *einode = dir->desc;
    u32 left;

    for (
        u32 offset = 0;
        true;
        offset += ptr->size, left -= ptr->size, ptr = (ext2_dentry_t *)((u32)ptr + ptr->size))
    {
        if (!buf || (u32)ptr >= (u32)buf->data + block_size)
        {
            brelse(buf);
            buf = bread(dir->dev, ext2_bmap(dir, offset / block_size, true), block_size);
            ptr = (ext2_dentry_t *)buf->data;
            left = block_size;
            assert(offset % block_size == 0);
        }
        if (offset >= dir->size)
        {
            einode->size += entry_size;
            dir->size = einode->size;
            ptr->nr = 0;
            ptr->size = entry_size;
            dir->buf->dirty = true;
        }
        if (ptr->nr)
            continue;
        if (ptr->size < entry_size)
            continue;
        if (left <= sizeof(ext2_dentry_t))
            ptr->size += left;
        *entry = ptr;
        memcpy(ptr->name, name, namelen);
        ptr->namelen = namelen;
        buf->dirty = true;
        return buf;
    }
}

int ext2_permission(inode_t *inode, int mask)
{
    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;

    u16 mode = einode->mode;

    if (!einode->nlinks)
        return false;

    task_t *task = running_task();
    user_t *user = user_get(task->uid);

    if (user->super)
        return true;

    if (task->uid == einode->uid)
        mode >>= 6;

    if ((mode & (u16)mask & 0b111) == mask)
        return true;
    return false;
}

int ext2_get_symlink(inode_t *inode, char **pathname, buffer_t **result)
{
    ext2_inode_t *einode = inode->desc;
    ext2_data_t *data = inode->data;

    buffer_t *buf = NULL;
    if (inode->size < sizeof(einode->zone))
    {
        *pathname = (char *)einode->zone;
        assert(*((char *)einode->zone + sizeof(einode->zone) - 1) == EOS);
    }
    else
    {
        assert(inode->size <= data->block_size);
        assert(einode->zone[0]);
        buf = bread(inode->dev, einode->zone[0], data->block_size);
        *pathname = (char *)buf->data;
        *result = buf;
        assert(*((char *)buf->data + data->block_size - 1) == EOS);
    }
    return inode->size;
}

int ext2_readlink(inode_t *inode, char *buf, int size)
{
    buffer_t *nbuf = NULL;
    char *pathname;

    // 得到 symname
    int len = ext2_get_symlink(inode, &pathname, &nbuf);

    size = MIN(size, len);

    memcpy(buf, pathname, size);

    brelse(buf);
    return size;
}

// 获得 inode 所链接到的 inode
int ext2_lnamei(inode_t *dir, inode_t *inode, inode_t **result)
{
    assert(ISLNK(inode->mode));

    char *pathname;
    buffer_t *buf = NULL;
    ext2_get_symlink(inode, &pathname, &buf);

    task_t *task = running_task();

    if (IS_SEPARATOR(pathname[0]))
    {
        dir = task->iroot;
        pathname++;
    }

    *result = nameid(pathname, dir);

    assert(*result);
    brelse(buf);
    return EOK;
}

int ext2_namei(inode_t *dir, char *name, char **next, inode_t **result, int flags)
{
    assert(name[0]);

    ext2_dentry_t *entry;
    buffer_t *buf = ext2_find_entry(&dir, name, next, &entry);
    if (!buf)
    {
        return EOF;
    }

    inode_t *inode = ext2_iget(dir->super, entry->nr);

    if (ISLNK(inode->mode) && (flags & NAMEI_FOLLOW))
    {
        inode_t *linode;
        ext2_lnamei(dir, inode, &linode);
        iput(inode);
        inode = linode;
    }

    *result = inode;

    brelse(buf);

    return EOK;
}

int ext2_symlink(inode_t *dir, char *name, char *symname)
{
    int ret = EOK;

    char *next = NULL;
    ext2_dentry_t *entry;
    buffer_t *ebuf = NULL;
    inode_t *inode = NULL;
    buffer_t *buf = NULL;

    inode_t *linode = lnamei(symname);
    if (!linode)
    {
        ret = -ENOENT;
        goto rollback;
    }

    ebuf = ext2_find_entry(&dir, name, &next, &entry);
    if (ebuf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    if (!ISDIR(linode->mode) && next)
    {
        ret = -EISDIR;
        goto rollback;
    }

    ebuf = ext2_add_entry(dir, name, &entry);
    entry->nr = ext2_ialloc(dir->super, INODE_TO_GROUP(dir->nr, ((ext2_desc_t *)dir->super->desc)));
    inode = ext2_new_inode(dir->super, entry->nr);

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;
    ext2_data_t *data = (ext2_data_t *)inode->data;

    einode->mode |= IFLNK;
    einode->nlinks++;

    char *pathname;
    int len = strlen(symname);
    if (len < sizeof(einode->zone))
    {
        pathname = (char *)einode->zone;
    }
    else if (len <= data->block_size)
    {
        buf = bread(inode->dev, ext2_bmap(inode, 0, true), data->block_size);
        pathname = (char *)buf->data;
        buf->dirty = true;
    }
    else
    {
        ret = -ERROR;
        goto rollback;
    }

    strcpy(pathname, symname);
    einode->size = len + 1;

rollback:
    brelse(ebuf);
    brelse(buf);
    iput(linode);
    iput(inode);
    return ret;
}

int ext2_mkdir(inode_t *dir, char *name, int mode)
{
    task_t *task = running_task();

    char *next;
    ext2_dentry_t *entry;
    buffer_t *ebuf = ext2_find_entry(&dir, name, &next, &entry);

    int ret = EOK;

    if (ebuf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    brelse(ebuf);

    ext2_desc_t *desc = dir->super->desc;
    ext2_data_t *data = dir->data;

    ebuf = ext2_add_entry(dir, name, &entry);
    entry->nr = ext2_ialloc(dir->super, INODE_TO_GROUP(dir->nr, desc));
    ebuf->dirty = true;

    inode_t *inode = ext2_new_inode(dir->super, entry->nr);

    ext2_inode_t *deinode = (ext2_inode_t *)dir->desc;

    deinode->nlinks++;
    dir->buf->dirty = true;;

    ext2_inode_t *ieinode = (ext2_inode_t *)inode->desc;
    ieinode->nlinks = 2;
    ieinode->mode = IFDIR | (mode & 0777 & ~task->umask);

    dentry_t *eentry;
    buffer_t *buf = ext2_add_entry(inode, ".", &eentry);
    eentry->nr = inode->nr;
    buf = ext2_add_entry(inode, "..", &eentry);
    eentry->nr = dir->nr;
    buf->count--;

    brelse(ebuf);
    brelse(buf);
    iput(inode);
    return ret;

rollback:
    brelse(ebuf);
    return ret;
}

bool ext2_dir_empty(inode_t *dir)
{
    ext2_data_t *data = dir->data;
    u32 block_size = data->block_size;

    ext2_dentry_t *ptr;
    buffer_t *buf = NULL;

    bool ret = true;

    for (
        u32 offset = 0;
        offset < dir->size;
        offset += ptr->size, ptr = (ext2_dentry_t *)((u32)ptr + ptr->size))
    {
        if (!buf || (u32)ptr >= (u32)buf->data + block_size)
        {
            brelse(buf);
            buf = bread(dir->dev, ext2_bmap(dir, offset / block_size, false), block_size);
            ptr = (ext2_dentry_t *)buf->data;
            assert(offset % block_size == 0);
        }
        if (!ptr->nr)
            continue;
        if (ptr->namelen != 1 && ptr->namelen != 2)
        {
            ret = false;
            goto rollback;
        }
        if (memcmp(ptr->name, ".", 1) && memcmp(ptr->name, "..", 2))
        {
            ret = false;
            goto rollback;
        }
    }

rollback:
    brelse(buf);
    return ret;
}

int ext2_rmdir(inode_t *dir, char *name)
{
    char *next = NULL;
    inode_t *inode = NULL;
    ext2_dentry_t *entry;
    buffer_t *ebuf = NULL;
    int ret = EOF;

    ebuf = ext2_find_entry(&dir, name, &next, &entry);
    if (!ebuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    inode = ext2_iget(dir->super, entry->nr);

    ext2_inode_t *ieinode = (ext2_inode_t *)inode->desc;
    if (!ISDIR(ieinode->mode))
    {
        ret = -ENOTDIR;
        goto rollback;
    }

    task_t *task = running_task();

    if (ieinode->mode & ISVTX && task->uid != ieinode->uid && !suser())
    {
        ret = -EPERM;
        goto rollback;
    }

    if (dir->super != inode->super)
    {
        ret = -ENOSYS;
        goto rollback;
    }

    if (inode->count > 1)
    {
        ret = -EBUSY;
        goto rollback;
    }

    if (!ext2_dir_empty(inode))
    {
        ret = -ENOTEMPTY;
        goto rollback;
    }

    assert(ieinode->nlinks == 2);

    ext2_truncate(inode);
    ext2_ifree(inode->super, inode->nr);

    ieinode->nlinks = 0;
    inode->buf->dirty = true;

    ext2_inode_t *deinode = (ext2_inode_t *)dir->desc;

    deinode->nlinks--;
    dir->buf->dirty = true;

    entry->nr = 0;
    memset(entry->name, 0, entry->namelen);
    ebuf->dirty = true;

    ret = EOK;

rollback:
    brelse(ebuf);
    iput(inode);
    return ret;
}

int ext2_rename(inode_t *odir, char *oldname, inode_t *ndir, char *newname)
{
    buffer_t *obuf = NULL;
    buffer_t *nbuf = NULL;
    inode_t *inode = NULL;

    int ret;

    char *next;
    dentry_t *entry;
    dentry_t *nentry;

    obuf = ext2_find_entry(&odir, oldname, &next, &entry);
    if (!obuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    inode = ext2_iget(odir->super, entry->nr);

    if (!ISDIR(inode->mode) && next)
    {
        iput(inode);
        inode = NULL;
    }

    if (inode == inode->super->iroot && inode != get_root_inode())
    {
        super_t *super = inode->super;
        iput(inode);
        inode = super->imount;
        inode->count++;
    }

    nbuf = ext2_find_entry(&ndir, newname, &next, &nentry);
    if (nbuf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    u32 nr = entry->nr;
    entry->nr = 0;
    obuf->dirty = true;

    nbuf = ext2_add_entry(ndir, newname, &nentry);

    nentry->nr = nr;

    ext2_inode_t *einode = inode->desc;
    einode->mtime = einode->atime = ktime();
    inode->mtime = inode->atime = einode->mtime;
    inode->buf->dirty = true;

rollback:
    brelse(nbuf);
    brelse(obuf);
    iput(inode);
    return ret;
}

int ext2_link(inode_t *odir, char *oldname, inode_t *ndir, char *newname)
{
    int ret = EOK;
    buffer_t *buf = NULL;
    char *next = NULL;
    inode_t *inode = NULL;

    if (ext2_namei(odir, oldname, &next, &inode, NAMEI_FOLLOW) == EOF)
        goto rollback;

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;

    // 不支持链接目录
    if (ISDIR(einode->mode))
    {
        ret = -EISDIR;
        goto rollback;
    }

    // 不支持跨分区 link
    if (ndir->super != inode->super)
    {
        ret = -ERROR;
        goto rollback;
    }

    ext2_dentry_t *entry;
    buf = ext2_find_entry(&ndir, newname, &next, &entry);
    if (buf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    buf = ext2_add_entry(ndir, newname, &entry);
    if (!buf)
    {
        ret = -ERROR;
        goto rollback;
    }

    entry->nr = inode->nr;
    buf->dirty = true;;
    einode->nlinks++;
    inode->buf->dirty = true;;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int ext2_unlink(inode_t *dir, char *name)
{
    task_t *task = running_task();
    int ret = EOK;
    buffer_t *ebuf = NULL;
    char *next = NULL;
    inode_t *inode = NULL;

    ext2_dentry_t *entry;
    ebuf = ext2_find_entry(&dir, name, &next, &entry);
    if (!ebuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    if (next)
    {
        ret = -EISDIR;
        goto rollback;
    }

    inode = ext2_iget(dir->super, entry->nr);

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;

    if (ISDIR(einode->mode))
    {
        ret = -EISDIR;
        goto rollback;
    }

    if (dir->super != inode->super)
    {
        ret = -ENOSYS;
        goto rollback;
    }

    if (einode->mode & ISVTX && einode->uid != task->uid && !suser())
    {
        ret = -EPERM;
        goto rollback;
    }

    assert(einode->nlinks);

    entry->nr = 0;
    ebuf->dirty = true;;;

    einode->nlinks--;
    inode->buf->dirty = true;;

    if (!einode->nlinks)
    {
        ext2_truncate(inode);
        ext2_ifree(inode->super, inode->nr);
    }

rollback:
    brelse(ebuf);
    iput(inode);
    return ret;
}

int ext2_mknod(inode_t *dir, char *name, int mode, int dev)
{
    int ret = EOK;

    char *next = NULL;
    buffer_t *buf = NULL;
    inode_t *inode = NULL;

    ext2_dentry_t *entry;
    buf = ext2_find_entry(&dir, name, &next, &entry);
    if (buf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    buf = ext2_add_entry(dir, name, &entry);
    if (!buf)
    {
        ret = -ERROR;
        goto rollback;
    }

    entry->nr = ext2_ialloc(dir->super, INODE_TO_GROUP(inode->nr, ((ext2_desc_t *)inode->super->desc)));
    buf->dirty = true;;
    inode = ext2_new_inode(dir->super, entry->nr);
    if (!inode)
    {
        ret = -ERROR;
        goto rollback;
    }

    ext2_inode_t *einode = (ext2_inode_t *)inode->desc;

    einode->nlinks++;
    einode->mode = mode;

    einode->zone[0] = dev;
    inode->rdev = dev;

    inode->buf->dirty = true;;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int ext2_read_super(dev_t dev, super_t *super)
{
    ext2_desc_t *desc = NULL;
    buffer_t *buf = NULL;

    buf = bread(dev, 1, 1024);
    desc = (ext2_desc_t *)buf->data;
    if (desc->magic != EXT2_MAGIC)
        goto rollback;

    if (desc->state != EXT2_FS_S_CLEAN)
        goto rollback;

    if (desc->required_features != EXT2_R_F_TYPE_FIELD)
        goto rollback;

    if (div_round_up(desc->total_blocks, desc->group_blocks) != div_round_up(desc->total_inodes, desc->group_inodes))
        goto rollback;

    if (desc->version_major < 1)
        goto rollback;

    super->dev = dev;
    super->desc = desc;
    super->buf = buf;

    super->data = (void *)alloc_kpage(1);
    ext2_data_t *data = super->data;
    data->block_size = 1024 << desc->block_size;
    data->inode_size = desc->inode_size;
    data->groups_buf = bread(dev, desc->superblock + 1, data->block_size);
    data->total_groups = div_round_up(desc->total_blocks, desc->group_blocks);

    data->group_infos = kmalloc(sizeof(ext2_group_info_t) * data->total_groups);
    ext2_group_desc_t *groups = data->groups_buf->data;

    for (int i = 0; i < data->total_groups; i++)
    {
        ext2_group_info_t *info = &data->group_infos[i];
        info->desc = &groups[i];
        info->imap = bread(dev, info->desc->imap, data->block_size);
        info->zmap = bread(dev, info->desc->zmap, data->block_size);
    }

    super->iroot = ext2_iget(super, EXT2_ROOT_INODE);
    super->iroot->mount = super;

    return 0;

rollback:
    brelse(buf);
    return -EFSUNK;
}

int ext2_put_super(super_t *super)
{
    ext2_data_t *data = super->data;
    ext2_group_desc_t *groups = data->groups_buf->data;

    for (int i = 0; i < data->total_groups; i++)
    {
        ext2_group_info_t *info = &data->group_infos[i];
        info->desc = &groups[i];
        brelse(info->imap);
        brelse(info->zmap);
    }

    kfree(data->group_infos);
    brelse(data->groups_buf);
    free_kpage((u32)data, 1);
    brelse(super->buf);
}

fs_op_t ext2_op = {
    .read_super = ext2_read_super,
    .put_super = ext2_put_super,
    .mkfs = fs_default_nosys,

    .open = ext2_open,
    .close = ext2_close,

    .read = ext2_read,
    .write = ext2_write,
    .readdir = ext2_readdir,

    .lnamei = ext2_lnamei,
    .symlink = ext2_symlink,
    .readlink = ext2_readlink,

    .permission = ext2_permission,
    .namei = ext2_namei,
    .mkdir = ext2_mkdir,
    .rmdir = ext2_rmdir,
    .mknod = ext2_mknod,
    .link = ext2_link,
    .unlink = ext2_unlink,
    .rename = ext2_rename
};

void ext2_init()
{
    fs_register_op(FS_TYPE_EXT2, &ext2_op);
}